/*------------------------------------------------------------------------------
$module:      ObjArray
$package:     CseApi
$purpose:     TObjArray class definition
$author:      WAYNE WORK STDIO
$CreateAt:    08/07/04
$LastModify:
$license:     Mozilla Public License Version 1.1
------------------------------------------------------------------------------*/

#include "CseApi_.h"

extern CseObj dummy;

extern TCriticalSection *CseObjBuffLock;

extern CseObj *__EmptyBuff;
extern const TObjArray& CseEmptyBuff;

#ifndef CLOSE_REF_CHECK
  extern TInt32 gTotalObjArrayCount;
#endif

class TObjBuffLocker
{
public:
    TObjBuffLocker(void)  { CseObjBuffLock->enter(); }
    ~TObjBuffLocker(void) { CseObjBuffLock->leave(); }
};

STATIC CseObj *NewCseObjBlock(int n)
{
    if (n <= 0)
        return __EmptyBuff;
    else
    {
        int iLen = sizeof(TUint32[2]) + n * sizeof(CseObj);
        TUint32 *pLen = (TUint32 *) new char[iLen];
        pLen[0] = n;
        pLen[1] = n;
        CseObj *ret = (CseObj *) &pLen[2];

        CseObj *pObj = ret;
        for (int i = 0; i < n; i++,pObj++) *pObj = dummy;

        return ret;
    }
}

STATIC CseObj *NewCseObjBlock(const CseObj *pSour, int iLen, int iExt=0)
{
    int iNewLen = iLen + iExt;
    if (iNewLen <= 0 || (pSour == NULL && iLen > 0))
        return __EmptyBuff;
    else
    {
        int iNew = sizeof(TUint32[2]) + iNewLen * sizeof(CseObj);
        TUint32 *pLen = (TUint32 *) new char[iNew];
        CseObj *ret = (CseObj *) &pLen[2];

        // copy string context
        pLen[0] = iNewLen;
        pLen[1] = iLen;
        CseObj *pObj = ret;
        for (int i = 0; i < iLen; i++,pObj++,pSour++)
            *pObj = Cse_IncRefEx(*pSour);

        return ret;
    }
}

STATIC void TryFreeObjBlock(CseObj *pObj)
{
    if (pObj != __EmptyBuff)
    {
        TUint32 *pLen = (TUint32 *)((char *)pObj - sizeof(TUint32[2]));
        int iMax = pLen[1] - 1;
        pObj += iMax;
        for (int i = iMax; i >= 0; i--, pObj--)
        {   // the last one free first, more friendly as stack style
            CseObj v = *pObj;
            *pObj = NULL;      // avoid recursive free
            Cse_DecRef(v);
        }
        
        delete []pLen;
    }
}

TObjArray::TObjArray()
{
    m_data = __EmptyBuff;
#ifndef CLOSE_REF_CHECK
    gTotalObjArrayCount++;
#endif
}

TObjArray::TObjArray(int MagicNum, TObjArray& bArray)
{
    CHECK_MSG(MagicNum == 20051206,_T("unauthorized call"));

    m_data = bArray.transfer(__EmptyBuff);
#ifndef CLOSE_REF_CHECK
    gTotalObjArrayCount++;
#endif
}

TObjArray::TObjArray(const TObjArray& bArray)
{
    TObjBuffLocker lock;
    m_data = NewCseObjBlock(bArray.m_data,bArray.len());
#ifndef CLOSE_REF_CHECK
    gTotalObjArrayCount++;
#endif
}

TObjArray::TObjArray(int count)
{
    m_data = NewCseObjBlock(count);
#ifndef CLOSE_REF_CHECK
    gTotalObjArrayCount++;
#endif
}

TObjArray::TObjArray(const CseObj* pSour, int n, int iExt)
{
    TObjBuffLocker lock;
    m_data = NewCseObjBlock(pSour,n,iExt);
#ifndef CLOSE_REF_CHECK
    gTotalObjArrayCount++;
#endif
}

TObjArray::~TObjArray()
{
    TObjBuffLocker lock;
    TryFreeObjBlock(m_data);
#ifndef CLOSE_REF_CHECK
    gTotalObjArrayCount--;
#endif
}

TObjArray& TObjArray::joinEx(const CseObj *pSour, int iSourLen)
{
    if (iSourLen <= 0) return *this;  // no need join

    TUint32 *pLen = (TUint32 *) ((char *)m_data - sizeof(TUint32[2]));
    int iTargLen = pLen[1];
    int iTotal   = iTargLen + iSourLen;

    if ((int)pLen[0] >= iTotal)
    {   // have enough space
        CseObj *pTarg = m_data + iTargLen;
        for (int i = 0; i < iSourLen; i++,pTarg++,pSour++)
            *pTarg = Cse_IncRefEx(*pSour);

        pLen[1] = iTotal;
    }
    else
    {   // add more space to hurry up continuous adding
        int iPad = (iTotal + 1) >> 1;  // (iTotal + 1) / 2

        // create enough space and join
        TObjArray ret = TObjArray(m_data,iTargLen,iSourLen + iPad);
        ret.join(pSour,iSourLen);

        // exchange result with self
        ret.m_data = transfer(ret.m_data);
    }
    return *this;
}

TObjArray& TObjArray::join(const TObjArray& bArray, int index)
{
    TObjBuffLocker lock;

    int iSourLen = bArray.len();
    if (index < 0)
    {
        index = iSourLen + index;
        if (index < 0) index = 0;
    }

    return joinEx(bArray.m_data+index,iSourLen-index);
}

TObjArray& TObjArray::join(const CseObj *pSour, int iSourLen)
{
    TObjBuffLocker lock;
    return joinEx(pSour,iSourLen);
}

TObjArray& TObjArray::operator=(const TObjArray& bArray)
{
    TObjBuffLocker lock;

    int iSourLen = bArray.len();
    TUint32 *pLen = (TUint32 *) ((char *)m_data - sizeof(TUint32[2]));

    if ((int)pLen[0] >= iSourLen)  // has enough space
    {   // first DecRef old object
        int iTargLen = pLen[1];
        CseObj *pTarg = m_data;
        int i;
        for (i = 0; i < iTargLen; i++,pTarg++)
        {
            CseObj v = *pTarg;
            *pTarg = dummy;
            Cse_DecRef(v);
        }

        // then copy new value
        pTarg = m_data;
        CseObj *pSour = bArray.m_data;
        for (i = 0; i < iSourLen; i++,pTarg++,pSour++)
            *pTarg = Cse_IncRefEx(*pSour);  // link of *pSour must have removed

        pLen[1] = iSourLen;
    }
    else
    {
        TryFreeObjBlock(m_data);
        m_data = NewCseObjBlock(bArray.m_data,iSourLen);
    }
    return *this;
}

void TObjArray::operator+=(const TObjArray& bArray)
{
    TObjBuffLocker lock;
    joinEx(bArray.m_data,bArray.len());
}

void TObjArray::operator+=(const TCseObj& obj)
{
    CseObj *pValue = (CseObj *)&obj;
    join(pValue,1);
}

TObjArray TObjArray::vformat(int ArgNum, va_list ArgPtr)
{
    TObjArray ret;
    for (int iCount = 0; iCount < ArgNum; iCount++)
        ret.append(va_arg(ArgPtr,CseObj));

    return TObjArray(CUT_FROM,ret);
}

TObjArray TObjArray::format(int ArgNum, ...)
{
    va_list pArg;
    va_start(pArg, ArgNum);
    
    TObjArray ret;
    for (int iCount = 0; iCount < ArgNum; iCount++)
        ret.append(va_arg(pArg,CseObj));
    va_end(pArg);

    return TObjArray(CUT_FROM,ret);
}

TObjArray TObjArray::multi(int n) const
{
    int iSourLen = len();
    int iTargLen = n * iSourLen;
    if (iTargLen <= 0) return CseEmptyBuff;

    TObjArray ret;
    ret.m_data = NewCseObjBlock(iTargLen);
    *(TUint32 *)((char *)ret.m_data - sizeof(TUint32)) = 0;

    TObjBuffLocker lock;
    for (int i = 0; i < n; i++)
        ret.joinEx(m_data,iSourLen);
    return TObjArray(CUT_FROM,ret);
}
    
TCseObj& TObjArray::sub(int index) const
{
    TObjBuffLocker lock;

    int iLen = len();
    if (index < 0) index = index + iLen;

    CHECK_MSG(index >= 0 && index < iLen,_T("access TObjArray out of range"));
    return (TCseObj&) m_data[index];
}

TCseObj& TObjArray::operator[](int index) const
{
    TObjBuffLocker lock;

    int iLen = len();
    if (index < 0) index = index + iLen;

    CHECK_MSG(index >= 0 && index < iLen,_T("access TObjArray out of range"));
    return (TCseObj&) m_data[index];
}

CseObj *TObjArray::addr() const
{
    return m_data;
}

int TObjArray::len() const
{
    return CSE_ARRAY_LEN(m_data);
}

TBool TObjArray::isEmpty() const
{
    return len() <= 0;
}

TInt TObjArray::compare(const TObjArray& other) const
{
    TObjBuffLocker lock;

    if (m_data == other.m_data)
        return 0;
    else
    {
        int iSelfLen = len();
        int iOtherLen = other.len();

        for (int i = 0; i < iSelfLen; i++)
        {
            if (i >= iOtherLen) return 1; // self array large than other
            
            CseObj v = Cse_unlink(other.m_data[i]);
            TInt iRet = Cse_ObjComp(Cse_unlink(m_data[i]),v,Cse_type(v));
            if (iRet != 0) return iRet;
        }

        if (iSelfLen == iOtherLen)
            return 0;
        else return -1;  // self array less than other
    }
}

TObjArray TObjArray::slice(int index, int iLen) const
{
    TObjBuffLocker lock;

    int nLen = len();
    if (index < 0)
    {
        index = index + nLen;
        if (index < 0) index = 0;
    }

    if (index >= nLen || iLen == 0)
        return TObjArray();
    else
    {
        int iLeft = nLen - index;
        if (iLen == -1 || iLen > iLeft) iLen = iLeft;
        return TObjArray(m_data + index,iLen);
    }
}

TBool TObjArray::copyFrom(const CseObj* pSour, int iLen, int iOff, TBool RefInc) const
{
    if (iOff + iLen > len()) return false;

    CseObj *pTarg = m_data + iOff;
    if (pTarg > pSour)
    {   // avoid backward overwrite
        pTarg += iLen - 1;
        pSour += iLen - 1;
        for (int i = iLen-1; i >= 0; i--,pTarg--,pSour--)
        {
            ASSERT_TRUE(*pSour != NULL);
            
            CseObj v = *pSour;
            if (RefInc) Cse_IncRef(v);
            *pTarg = v;
        }
    }
    else if (pTarg != pSour)
    {   // avoid forward overwrite
        for (int i = 0; i < iLen; i++,pTarg++,pSour++)
        {
            ASSERT_TRUE(*pSour != NULL);
            
            CseObj v = *pSour;
            if (RefInc) Cse_IncRef(v);
            *pTarg = v;
        }
    }
    // else, pTarg == pSour, // no need copy
    return true;
}

CseObj *TObjArray::transfer(CseObj *other)
{
    CseObj *ret = m_data;
    if (NULL == other)
        m_data = __EmptyBuff;
    else
    {
        TUint32 *pLen = (TUint32 *)((char *)other - sizeof(TUint32[2]));
        ASSERT_TRUE(pLen[0] >= pLen[1]);

        m_data = other;
    }
    return ret;
}

void TObjArray::exchange(TObjArray& other)
{
    TObjBuffLocker lock;
    other.transfer(transfer(other.addr()));
}

void TObjArray::insertEx(int iPos, const CseObj *pSour, int iLen)
{
    TUint32 *pLen = (TUint32 *)((char *)m_data - sizeof(TUint32[2]));
    int iTargLen = pLen[1];

    if (iPos == iTargLen)
    {
        joinEx(pSour,iLen);
        return;
    }

    int iTotal = iTargLen + iLen;

    if ((int)pLen[0] >= iTargLen + iLen)  // space is enough
    {
        pLen[1] = iTotal;
        copyFrom(m_data + iPos,iTargLen - iPos,iPos + iLen,false);
        copyFrom(pSour,iLen,iPos,true);
    }
    else
    {   // copy context
        TObjArray ret(iTotal);
        if (iPos > 0)
            ret.copyFrom(m_data,iPos,0,true);
        ret.copyFrom(pSour,iLen,iPos,true);
        ret.copyFrom(m_data + iPos,iTargLen - iPos,iPos + iLen,true);

        // exchange result and this
        ret.m_data = transfer(ret.m_data);
    }
}

const TCseObj& TObjArray::append(const TCseObj& obj)
{
    CseObj *pValue = (CseObj *)&obj;
    join(pValue,1);
    return obj;
}

TObjArray& TObjArray::add(const TCseObj& obj, int *pIndex)
{
    TObjBuffLocker lock;
    CseObj *pValue = (CseObj *)&obj;
    
    if (pIndex != NULL) *pIndex = len();
    return joinEx(pValue,1);
}

const TCseObj& TObjArray::insert(int index, const TCseObj& obj)
{
    TObjBuffLocker lock;

    int nLen = len();
    if (index < 0)
    {
        index = index + nLen;
        if (index < 0) index = 0;
    }
    if (index > nLen) index = nLen;

    CseObj *pSour = (CseObj *)&obj;
    insertEx(index,pSour,1);
    
    return obj;
}

TCseObj TObjArray::pop(int index)
{
    TObjBuffLocker lock;

    int nLen = len();
    if (index < 0)
    {
        index = index + nLen;
        if (index < 0) index = 0;
    }
    if (index >= nLen || nLen <= 0) return dummy;

    CseObj vTmp = *(m_data + index);
    TCseObj ret = vTmp;
    
    if (nLen <= 1)
    {   // free old buffer
        TryFreeObjBlock(m_data);
        m_data = __EmptyBuff;
    }
    else
    {
        Cse_DecRef(vTmp);
        copyFrom(m_data + index + 1,nLen - index - 1,index,false);
        
        TUint32 *pNewLen = (TUint32 *)((char *)m_data - sizeof(TUint32));
        *pNewLen = nLen - 1;
    }
    return ret;
}

void TObjArray::remove(int index, int iLen)
{
    TObjBuffLocker lock;

    int nLen = len();
    if (index < 0)
    {
        index = index + nLen;
        if (index < 0) index = 0;
    }
    if (index >= nLen || iLen <= 0) return;

    int iLeft = nLen - index;     // max iLeft items can be deleted
    if (iLen > iLeft) iLen = iLeft;
    int iNewLen = nLen - iLen;

    if (iNewLen <= 0)
    {   // free old buffer
        TryFreeObjBlock(m_data);
        m_data = __EmptyBuff;
    }
    else
    {
        CseObj *pTarg = m_data + index;
        for (int i = 0; i < iLen; i++,pTarg++)
        {
            CseObj v = *pTarg;
            *pTarg = dummy;
            Cse_DecRef(v);
        }

        copyFrom(m_data + index + iLen,nLen - index - iLen,index,false);
        TUint32 *pNewLen = (TUint32 *)((char *)m_data - sizeof(TUint32));
        *pNewLen = iNewLen;
    }
}

void TObjArray::clear()
{
    TObjBuffLocker lock;
    TryFreeObjBlock(m_data);
    m_data = __EmptyBuff;
}

int TObjArray::truncate(int iLen) const
{
    TObjBuffLocker lock;

    int nLen = len();
    if (iLen < nLen)
    {
        TUint32 *pNewLen = (TUint32 *)((char *)m_data - sizeof(TUint32));

        CseObj *pTarg = m_data + iLen;
        for (int i = iLen; i < nLen; i++,pTarg++)
        {
            CseObj v = *pTarg;
            *pTarg = dummy;
            Cse_DecRef(v);
        }

        *pNewLen = iLen;  // if iLen == 0, will not free buffer
        return iLen;
    }
    else return nLen;
}

STATIC int FindSortIndex(CseObj *m_data, int iHigh, CseObj vValue)
{
    int iLow = 0;
    int index = -1;

    while (iLow <= iHigh)
    {
        int iHalf = (iLow + iHigh) / 2;
        TInt i = Cse_ObjComp(Cse_unlink(m_data[iHalf]),vValue,Cse_type(vValue));

        if (i < 0)
            iLow = iHalf + 1;
        else if (i == 0)
        {
            index = iHalf;
            break;
        }
        else iHigh = iHalf - 1;
    }
    if (index == -1) index = iLow;

    return index;
}

int TObjArray::sortAdd(const TCseObj& obj)
{
    TObjBuffLocker lock;

    CseObj *pOrg = (CseObj *)&obj;
    int index = FindSortIndex(m_data,len()-1,Cse_unlink(*pOrg));
    insertEx(index,pOrg,1);
    return index;
}

void TObjArray::sort() const
{
    TObjBuffLocker lock;

    int iCount;
    int iLen = len();
    if (iLen <= 1) return; // no need to sort

    // first make empty-copy and sort-add every item
    TObjArray bTmp(NULL,0,iLen);
    CseObj *pSour = addr();
    CseObj *pTarg = bTmp.addr();
    for (iCount = 0; iCount < iLen; iCount++,pSour++)
        bTmp.insertEx(FindSortIndex(pTarg,iCount-1,*pSour),pSour,1);

    // then copy back context
    pSour = pTarg;
    pTarg = addr();
    for (iCount = 0; iCount < iLen; iCount++,pSour++,pTarg++)
        *pTarg = *pSour;
}

int TObjArray::find(const TCseObj& sub, int from) const
{
    TObjBuffLocker lock;

    int iLen = len();
    if (from >= iLen) return -1;
    
    if (from < 0) from = iLen + from;
    if (from < 0) from = 0;

    CseObj vSub = Cse_unlink(*(CseObj *)&sub);
    for (int i = from; i < iLen; i++)
    {
        CseObj v = Cse_unlink(m_data[i]);
        if (Cse_equal(vSub,v))
            return i;
    }
    return -1;
}

void TObjArray::resize(int iSize)
{
    TObjBuffLocker lock;
    
    int iLen = len();
    if (iSize < iLen)
        truncate(iSize);
    else if (iSize > iLen)
    {
        for (int i = iSize - iLen; i > 0; i--)
            joinEx(&dummy,1);
    }
}

TCharArray TObjArray::str() const
{
    TObjBuffLocker lock;

    TCharArray ret(_T("["));
    int iLen = len();

    CseObj *pTarg = m_data;
    for (int i = 0; i < iLen; i++,pTarg++)
    {
        TCharArray sTmp;
        Cse_str(sTmp,Cse_unlink(*pTarg));

        if (i == 0)
            ret.join(sTmp);
        else ret.join(_T(',')).join(sTmp);
    }
    ret += _T(']');

    return TCharArray(CUT_FROM,ret);
}

TBool TObjArray::enumItem(void*& pIter, TCseObj *pRet) const
{
    TObjBuffLocker lock;

    if (pIter == NULL)  // start enumlate
        pIter = m_data;
    else pIter = (CseObj *)pIter + 1;

    // maybe the dict has resize
    if ((CseObj *)pIter < m_data) return false;
    CseObj *pEnd = m_data + CSE_ARRAY_LEN(m_data);
    if ((CseObj *)pIter < pEnd)
    {
        if (pRet != NULL) *pRet = *(CseObj *)pIter;
        return true;
    }
    else return false;
}

TBool TObjArray::operator==(const TObjArray& y) const
{
    return compare(y) == 0;
}

TBool TObjArray::operator!=(const TObjArray& y) const
{
    return compare(y) != 0;
}

TBool TObjArray::operator>(const TObjArray& y) const
{
    return compare(y) > 0;
}

TBool TObjArray::operator>=(const TObjArray& y) const
{
    return compare(y) >= 0;
}

TBool TObjArray::operator<(const TObjArray& y) const
{
    return compare(y) < 0;
}

TBool TObjArray::operator<=(const TObjArray& y) const
{
    return compare(y) <= 0;
}
